/*
 *    Copyright 2020 ZhangYi
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package cn.zy.mydashboard;

import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Shader;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.OvershootInterpolator;

/**
 * 有点儿个性的仪表盘
 *
 * @author Yi
 * @since 2020-3-22 09:18:51
 */
public class DashboardView extends View {

    /**
     * 起点默认颜色
     */
    private static final int DEFAULT_START_COLOR = Color.parseColor("#345BAD");

    /**
     * 终点默认颜色
     */
    private static final int DEFAULT_END_COLOR = Color.parseColor("#BB0CA2");

    /**
     * 指针默认颜色
     */
    private static final int DEFAULT_POINTER_COLOR = Color.parseColor("#E8E5E9");

    private Point center = new Point(); // 画布中点坐标

    /**
     * 刻度参数
     */
    private int divideCount; // 刻度数量
    private int divideMaxValue; // 刻度最大值
    private Paint dividePaint = new Paint(); // 刻度画笔

    /**
     * 刻度值参数
     */
    private int divideValueGap; // 刻度到指示环间距
    private int divideValueSize; // 刻度值字体大小
    private int divideValue; // 刻度输入值
    private Paint divideValuePaint = new Paint(); // 刻度值画笔

    /**
     * 指示环参数
     */
    private int ringRadius; // 指示环半径
    private int ringWidth; // 指示环宽度
    private float ringStartAngle; // 指示环起点角度
    private float ringSweepAngle; // 指示环划过角度
    private int ringStartColor; // 指示环起点颜色
    private int ringEndColor; // 指示环终点颜色
    private Paint ringPaint = new Paint(); // 指示环画笔

    /**
     * 指针参数
     */
    private int pointerLength; // 指针长度
    private int pointerColor; // 指针颜色
    private Paint pointerPaint = new Paint(); // 指针画笔

    /**
     * 文本参数
     */
    private int textSize; // 文本字体大小
    private String textFormatter; // 文本格式器
    private Paint textPaint = new Paint(); // 文本画笔

    /**
     * 可变参数
     */
    private float prevPer; // 起始百分比
    private float nextPer; // (过程中)终止百分比

    public DashboardView(Context paramContext, AttributeSet attr) {
        this(paramContext, attr, 0);
    }

    public DashboardView(Context paramContext, AttributeSet attr, int paramInt) {
        super(paramContext, attr, paramInt);
        init(attr);
    }

    private void init(AttributeSet attr) {
        TypedArray typedArray = getContext().obtainStyledAttributes(attr, R.styleable.DashboardView);
        ringWidth = typedArray.getDimensionPixelSize(R.styleable.DashboardView_ringWidth, 40);
        ringStartAngle = typedArray.getFloat(R.styleable.DashboardView_ringStartAngle, 170f);
        ringSweepAngle = typedArray.getFloat(R.styleable.DashboardView_ringSweepAngle, 200f);
        ringStartColor = typedArray.getColor(R.styleable.DashboardView_ringStartColor, DEFAULT_START_COLOR);
        ringEndColor = typedArray.getColor(R.styleable.DashboardView_ringEndColor, DEFAULT_END_COLOR);
        divideCount = typedArray.getInt(R.styleable.DashboardView_divideCount, 10);
        divideMaxValue = typedArray.getInt(R.styleable.DashboardView_divideMaxValue, 100);
        divideValueSize = typedArray.getDimensionPixelSize(R.styleable.DashboardView_divideValueSize, 40);
        divideValueGap = typedArray.getDimensionPixelSize(R.styleable.DashboardView_divideValueGap, 10);
        divideValue = typedArray.getInt(R.styleable.DashboardView_divideValue, 0);
        pointerLength = typedArray.getDimensionPixelSize(R.styleable.DashboardView_pointerLength, 100);
        pointerColor = typedArray.getColor(R.styleable.DashboardView_pointerColor, DEFAULT_POINTER_COLOR);
        textSize = typedArray.getDimensionPixelSize(R.styleable.DashboardView_textSize, 40);
        textFormatter = typedArray.getString(R.styleable.DashboardView_textFormatter);
        if (TextUtils.isEmpty(textFormatter)) {
            textFormatter = "%d";
        }
        typedArray.recycle();

        ringPaint.setAntiAlias(true);
        ringPaint.setStyle(Paint.Style.STROKE);
        ringPaint.setStrokeCap(Paint.Cap.ROUND);

        dividePaint.setAntiAlias(true);
        dividePaint.setColor(-1);

        divideValuePaint.setAntiAlias(true);
        divideValuePaint.setTextSize(divideValueSize);
        divideValuePaint.setTextAlign(Paint.Align.CENTER);

        pointerPaint.setAntiAlias(true);

        textPaint.setAntiAlias(true);
        textPaint.setTextSize(textSize);
        textPaint.setTextAlign(Paint.Align.CENTER);

        checkDivideValue();
        nextPer = divideValue * 1f / divideMaxValue;
        checkEndPer();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int size = View.MeasureSpec.getSize(widthMeasureSpec);
        setMeasuredDimension(size, size);
        center.set(size / 2, size / 2);
        ringRadius = size / 2 - ringWidth - divideValueSize - divideValueGap;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawRing(canvas);
        drawDivide(canvas);
        drawPointer(canvas);
        if (textSize > 0) {
            drawText(canvas);
        }
    }

    /**
     * 绘制文本
     */
    private void drawText(Canvas canvas) {
        canvas.save();

        canvas.translate(center.x, center.y + textSize / 2f);
        textPaint.setColor(nextPer == 0f ? pointerColor : getCurrentColor(nextPer));
        canvas.drawText(String.format(textFormatter, divideValue), 0f, 0f, textPaint);

        canvas.restore();
    }

    /**
     * 绘制指针
     */
    private void drawPointer(Canvas canvas) {
        canvas.save();

        canvas.translate(center.x, center.y);
        canvas.rotate(-90f - (180f - ringStartAngle) + nextPer * ringSweepAngle);
        Path path = new Path();
        path.moveTo(0f, -(pointerLength + ringRadius / 3f));
        path.lineTo(-5f, -ringRadius / 3f);
        path.lineTo(5f, -ringRadius / 3f);
        path.lineTo(0f, -(pointerLength + ringRadius / 3f));
        path.close();
        pointerPaint.setColor(nextPer == 0f ? pointerColor : getCurrentColor(nextPer));
        canvas.drawPath(path, pointerPaint);

        canvas.restore();
    }

    /**
     * 绘制刻度
     */
    private void drawDivide(Canvas canvas) {
        canvas.save();

        canvas.translate(center.x, center.y);
        canvas.rotate(-90f - (180f - ringStartAngle));
        float divide_start_y = -(ringRadius + ringWidth / 2f);
        int divide_value_step = divideMaxValue / divideCount;
        float rotate_step = ringSweepAngle / divideCount;
        for (int i = 0; i <= divideCount; i++) {
            if (i % 2 == 0) { // 长刻度
                dividePaint.setStrokeWidth(4f);
                canvas.drawLine(0f, divide_start_y, 0f, divide_start_y + ringWidth / 3f * 2f, dividePaint);

                // 刻度值
                divideValuePaint.setColor(getCurrentColor(i * 1f / divideCount));
                canvas.drawText(String.valueOf(i * divide_value_step), 0, divide_start_y - divideValueGap, divideValuePaint);
            } else { // 短刻度
                dividePaint.setStrokeWidth(2f);
                canvas.drawLine(0f, divide_start_y, 0f, divide_start_y + ringWidth / 2f, dividePaint);
            }
            canvas.rotate(rotate_step);
        }

        canvas.restore();
    }

    /**
     * 绘制指示环
     */
    private void drawRing(Canvas canvas) {
        canvas.save();

        canvas.translate(center.x, center.y);
        /* 绘制指示环 */
        ringPaint.setStrokeWidth(ringWidth);
        // 线性渐变
        double radians = Math.PI / 180d * (180d - ringStartAngle);
        PointF start_point = getCoordinate(radians, ringRadius); // 起点坐标
        PointF end_point = new PointF(-start_point.x, start_point.y); // 终点坐标
        ringPaint.setShader(new LinearGradient(start_point.x, start_point.y, end_point.x, end_point.y,
                ringStartColor, ringEndColor, Shader.TileMode.CLAMP));
        canvas.drawArc(-ringRadius, -ringRadius,
                ringRadius, ringRadius,
                ringStartAngle, ringSweepAngle, false, ringPaint);

        /* 绘制指针背景环 */
        ringPaint.setColor(pointerColor);
        ringPaint.setStrokeWidth(ringWidth / 3f);
        ringPaint.setShader(null);
        float pointer_ring_radius = ringRadius / 3f;
        canvas.drawArc(-pointer_ring_radius, -pointer_ring_radius,
                pointer_ring_radius, pointer_ring_radius,
                ringStartAngle, ringSweepAngle, false, ringPaint);

        /* 绘制指针环 */
        PointF pointer_start_point = getCoordinate(radians, pointer_ring_radius); //起点坐标
        double pointer_radians = Math.PI / 180d * (nextPer * ringSweepAngle - (180d - ringStartAngle));
        PointF pointer_end_point = getCoordinate(pointer_radians, pointer_ring_radius); // 终点坐标
        ringPaint.setShader(new LinearGradient(pointer_start_point.x, pointer_start_point.y,
                pointer_end_point.x, pointer_end_point.y,
                ringStartColor, getCurrentColor(nextPer), Shader.TileMode.CLAMP));
        canvas.drawArc(-pointer_ring_radius, -pointer_ring_radius,
                pointer_ring_radius, pointer_ring_radius,
                ringStartAngle, nextPer * ringSweepAngle, false, ringPaint);

        canvas.restore();
    }

    /**
     * 计算线性渐变起点、终点坐标
     *
     * @param radians 角度（弧度）
     * @param length  斜边边长
     */
    private PointF getCoordinate(double radians, double length) {
        PointF pointF = new PointF();
        pointF.set(-(float) (Math.cos(radians) * length), (float) (Math.sin(radians) * length));
        return pointF;
    }

    /**
     * 计算过渡色值
     *
     * @param fraction 差值百分比 0f~1f
     * @return 过渡色值
     */
    private int getCurrentColor(float fraction) {
        int start_alpha = Color.alpha(ringStartColor);
        int start_red = Color.red(ringStartColor);
        int start_green = Color.green(ringStartColor);
        int start_blue = Color.blue(ringStartColor);

        int end_alpha = Color.alpha(ringEndColor);
        int end_red = Color.red(ringEndColor);
        int end_green = Color.green(ringEndColor);
        int end_blue = Color.blue(ringEndColor);

        int final_alpha = (int) (start_alpha + (end_alpha - start_alpha) * fraction);
        int final_red = (int) (start_red + (end_red - start_red) * fraction);
        int final_green = (int) (start_green + (end_green - start_green) * fraction);
        int final_blue = (int) (start_blue + (end_blue - start_blue) * fraction);
        return Color.argb(final_alpha, final_red, final_green, final_blue);
    }

    public DashboardView setDivideCount(int divideCount) {
        this.divideCount = divideCount;
        return this;
    }

    public DashboardView setDivideMaxValue(int divideMaxValue) {
        this.divideMaxValue = divideMaxValue;
        return this;
    }

    public DashboardView setDivideValueGap(int divideValueGap) {
        this.divideValueGap = divideValueGap;
        return this;
    }

    public DashboardView setDivideValueSize(int divideValueSize) {
        this.divideValueSize = divideValueSize;
        return this;
    }

    public DashboardView setRingRadius(int ringRadius) {
        this.ringRadius = ringRadius;
        return this;
    }

    public DashboardView setRingWidth(int ringWidth) {
        this.ringWidth = ringWidth;
        return this;
    }

    public DashboardView setRingStartAngle(float ringStartAngle) {
        this.ringStartAngle = ringStartAngle;
        return this;
    }

    public DashboardView setRingSweepAngle(float ringSweepAngle) {
        this.ringSweepAngle = ringSweepAngle;
        return this;
    }

    public DashboardView setRingStartColor(int ringStartColor) {
        this.ringStartColor = ringStartColor;
        return this;
    }

    public DashboardView setRingEndColor(int ringEndColor) {
        this.ringEndColor = ringEndColor;
        return this;
    }

    public DashboardView setPointerLength(int pointerLength) {
        this.pointerLength = pointerLength;
        return this;
    }

    public DashboardView setTextSize(int textSize) {
        this.textSize = textSize;
        return this;
    }

    public DashboardView setTextFormatter(String textFormatter) {
        this.textFormatter = textFormatter;
        return this;
    }

    public void setDivideValue(int divideValue) {
        this.divideValue = divideValue;
        checkDivideValue();

        prevPer = nextPer;
        nextPer = divideValue * 1f / divideMaxValue;
        nextPer();
    }

    private void checkDivideValue() {
        if (divideValue < 0) {
            divideValue = 0;
        } else if (divideValue > divideMaxValue) {
            divideValue = divideMaxValue;
        }
    }

    private void nextPer() {
        ValueAnimator animator = ValueAnimator.ofFloat(prevPer, nextPer);
        animator.setDuration(1000);
        animator.setInterpolator(new OvershootInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                nextPer = (float) animation.getAnimatedValue();
                checkEndPer();
                invalidate();
            }
        });
        animator.start();
    }

    private void checkEndPer() {
        if (nextPer < 0f) {
            nextPer = 0f;
        } else if (nextPer > 1f) {
            nextPer = 1f;
        }
    }
}
